# -*- coding: utf-8 -*-
"""
Created on Mon Jun 18 14:47:08 2012

@author: cedric
"""
from math import *
from random import *

#======================================
#--------------------------------------
class Lambda(object):
	def __init__(self, fLambda, nParams, format):
		self.fLambda = fLambda
		self.nParams = nParams
		self.formatString = format

	def getNParams(self):
		return self.nParams #TODO may be we can guess that from lambda

	def toString(self, params):
		if(self.nParams == 1):
			return self.formatString % params[0].__str__() #TODO fint out the right syntax
		elif(self.nParams == 2):
			return self.formatString % (params[0].__str__(), params[1].__str__())
		elif(self.nParams == 3):
			return self.formatString % (params[0].__str__(), params[1].__str__(), params[2].__str__())
		else:
			return "?"

def eventuallyChangeNull(x):
	return x+(x==0)*0.0001

def eventuallyChangeNullOrOne(x):
	return x+(x==0 or x==1)*0.0001


hashLambdas = {
			"sin":Lambda(lambda x: sin(x), 1, "sin(%s)"),
			"cos":Lambda(lambda x: cos(x), 1, "cos(%s)"),
			"tan":Lambda(lambda x: tan(x), 1, "tan(%s)"),
			#"asin":Lambda(lambda x: asin(x), 1, "asin(%s)"),
			#"acos":Lambda(lambda x: acos(x), 1, "acos(%s)"),
			"atan":Lambda(lambda x: atan(x), 1, "atan(%s)"),
			"abs":Lambda(lambda x: abs(x), 1, "abs(%s)"),
			"plus":Lambda(lambda x,y: x + y, 2, "(%s)+(%s)"),
			"minus":Lambda(lambda x,y: x - y, 2, "(%s)-(%s)"),
			"times":Lambda(lambda x,y: x * y, 2, "(%s)*(%s)"),
			#"div":Lambda(lambda x,y: x / eventuallyChangeNull(y), 2, "(%s)/(%s)"),
			#"mod":Lambda(lambda x,y: x % eventuallyChangeNull(y), 2, "(%s) mod (%s)"),
			"div":Lambda(lambda x,y: x / y, 2, "(%s)/(%s)"),
			"mod":Lambda(lambda x,y: x % y, 2, "(%s) mod (%s)"),
			"pow":Lambda(lambda x,y: x ** round(y), 2, "(|%s|)^(%s)"),
			"absPow":Lambda(lambda x,y: abs(x) ** y, 2, "(|%s|)^(%s)"),
			#"pow":Lambda(lambda x,y: abs(x) ** y, 2, "(|%s|)^(%s)"),
			"log":Lambda(lambda x,y: log(abs(x), abs(y)), 2, "log(|%s|,|%s|)"),
			#"log":Lambda(lambda x,y: log(eventuallyChangeNull(abs(x)),eventuallyChangeNullOrOne(abs(y))), 2, "log(|%s|,|%s|)"),
			"inf":Lambda(lambda x,y: x<y, 2, "(%s)<(%s)")
			#"identity":Lambda(lambda x,a,b: x>=a and x<=b, 3, "(%s)E[%s,%s]")
}
lambdas = []
for key in hashLambdas.keys():
	lambdas.append(hashLambdas[key])
#======================================
#--------------------------------------
class NullFormula(object):
	def __init__(self, gene):
		self.gene = gene
		self.gene.register(self)
		self.parentFormula = None

	def isNull(self):
		return True

	def setParent(self, parentFormula):
		self.parentFormula = parentFormula


	def __str__(self):
		return "Null"
	#TODO something to let know there is no parent in an efficient way

	#def insertBack(self, formula):
		#formula.setParent(self.parentFormula)
		#self.setParent(formula)
			

	def replaceChild(self, old, new):
		pass

	def clone(self, newGene):
		return NullFormula(newGene)

	def restartRegistration(self, gene):
		pass


#debug = open("debug.txt", 'w')
class Formula(NullFormula):
	def __init__(self, gene, func, childFormulas):
		super(Formula, self).__init__(gene)
		self.func = func
		self.childFormulas = childFormulas
		self.parentFormula = NullFormula(gene)
		if childFormulas != None:
			for child in self.childFormulas:
				#print child, '|', self
				#if not child.parentFormula.isNull():
					#child.insertBack(self)
				#else:
				child.setParent(self)

	def restartRegistration(self, gene):
		self.gene = gene
		self.gene.register(self)
		if self.childFormulas != None:
			for child in self.childFormulas:
				child.restartRegistration(gene)

	def isNull(self):
		return False

	def isCte(self):
		return type(self) == ConstanteFormula #Try yo optimase by directly returning True or False

	def isX(self):
		return type(self) == XFormula

	def isFunc(self):
		return not self.isCte() and not self.isX()

	def __call__(self, x, y=None):
		#debug.write('------------\n')
		#debug.write('x:' + `x` + ',' + self.__str__() + '\n')
		#debug.write("self:" + `[self]` + '\n')
		#debug.write("self.childFormuls:" + `self.childFormulas` + '\n')
		nParams = self.func.getNParams()
		f = self.func.fLambda
		if(nParams == 1):
			return f(self.childFormulas[0](x))
		elif(nParams == 2):
			x1=self.childFormulas[0](x, y)
			y1=self.childFormulas[1](x, y)
			#print "both:", (x1, y1)
			return f(x1, y1)
			#return f(self.childFormulas[0](x), self.childFormulas[1](x))
		elif(nParams == 3):
			return f(self.childFormulas[0](x, y), self.childFormulas[1](x, y), self.childFormulas[2](x, y))
		else:
			return 0

	#def insertBack(self, formula):
		#super(Formula, self).insertBack(formula)
		#formula.parentFormula.replaceChild(self, formula)

	def replaceChild(self, old, new):
		if self.childFormulas != None:
			indexChild = self.childFormulas.index(old)
			self.childFormulas[indexChild] = new

	def __str__(self):
		return self.func.toString(self.childFormulas)

	def clone(self, newGene):
		clonedChilds = []
		for child in self.childFormulas:
			clonedChilds.append(child.clone(newGene))
		return Formula(newGene, self.func, clonedChilds)

class ConstanteFormula(Formula):
	def __init__(self, gene, constante):
		self.constante = constante
		super(ConstanteFormula, self).__init__(gene, None, None)

	def setConstante(self, constante):
		self.constante = constante

	def __call__(self, x, y=None):
		return self.constante

	def __str__(self):
		return `self.constante`

	def clone(self, newGene):
		return ConstanteFormula(newGene, self.constante)

class XFormula(Formula):
	def __init__(self, gene):
		#assert(var in VARIABLES)
		super(XFormula, self).__init__(gene, None, None)

	def __call__(self, x, y=None):
		return x

	def __str__(self):
		return 'x'

	def clone(self, newGene):
		return XFormula(newGene)

class YFormula(Formula):
	def __init__(self, gene):
		#assert(var in VARIABLES)
		super(YFormula, self).__init__(gene, None, None)

	def __call__(self, x, y=None):
		return y

	def __str__(self):
		return 'y'

	def clone(self, newGene):
		return YFormula(newGene)



if __name__ == '__main__':
	class Gene(object):
		def __init__(self):
			pass

		def register(self, formula):
			pass

	gene = Gene()
	f1 = XFormula(gene)
	f2 = Formula(gene, hashLambdas["cos"], [f1])
	#f1.insertBack(f2)
	print "f2 string :", f2
	print "f2(0) :", f2(0)
	print "f2(0.5) (wanted:0.877):", f2(0.5)
	f3 = XFormula(gene)
	f4 = Formula(gene, hashLambdas["tan"], [f3])
	#f3.insertBack(f4)
	f5 = Formula(gene, hashLambdas["plus"], [f2, f4])
	#f2.insertBack(f5)
	#f4.insertBack(f5)
	print "f5 string :", f5
	print "f5(0) :", f5(0)
	print "f5(0.5) (wanted:1.4238):", f5(0.5)

